#include <Python.h>

#include "ESystem.h"
#include "ELogModule.h"
#include "EFileModule.h"
#include "ETimeModule.h"
#include "EProfileModule.h"
#include "EMemAllocModule.h"

#include "EGraphicDevice.h"
#include "EGraphicContext.h"

#include "ESceneModule.h"
#include "EWorldSceneNode.h"

#include "ERenderQueueBuilder.h"
#include "EDebugRenderQueue.h"
#include "EDebugRenderStageModule.h"
#include "ECameraSceneNode.h"

#include "EKeyboardDevice.h"
#include "EMouseDevice.h"
#include "EMouseDeviceDriverGeneral.h"

#include "ESystemEventListener.h"
#include "ESystemEventDispatcher.h"

#include "EXml.h"
#include "EXmlHandler.h"

#include "ESceneBuffer.h"
#include "ESceneBufferColladaLoader.h"
#include "EGraphicCmdBuffer.h"

// platform_specific (Note : this header file must be included last time)
#include "platform_specific.h"

using namespace end1;
using namespace std;

class Entity
{
	public:
		Entity()
		{
		}

		~Entity()
		{
		}

		std::string id;
		float pos[3];
};

class World
{
	public:
		World()
		{
		}

		~World()
		{
		}

		void init()
		{
			/*
			      0------1
			     /      /|   z
			    /      / |   | / y
			   3------2  5   |/
			   |  4   | /    ----- x
			   |      |/
			   7------6

			*/

			bool is_ok = default_box_buffer_.assign(8, 24, EGeometryBuffer::E_LINE_LIST, true, false, false);
			EASSERT(is_ok);

			default_box_buffer_.setXYZ(0, -1.f, +1.f, +1.f);
			default_box_buffer_.setXYZ(1, +1.f, +1.f, +1.f);
			default_box_buffer_.setXYZ(2, +1.f, -1.f, +1.f);
			default_box_buffer_.setXYZ(3, -1.f, -1.f, +1.f);
			default_box_buffer_.setXYZ(4, -1.f, +1.f, -1.f);
			default_box_buffer_.setXYZ(5, +1.f, +1.f, -1.f);
			default_box_buffer_.setXYZ(6, +1.f, -1.f, -1.f);
			default_box_buffer_.setXYZ(7, -1.f, -1.f, -1.f);

			default_box_buffer_.setIndexBuffer(0, 0);
			default_box_buffer_.setIndexBuffer(1, 1);
			default_box_buffer_.setIndexBuffer(2, 1);
			default_box_buffer_.setIndexBuffer(3, 2);
			default_box_buffer_.setIndexBuffer(4, 2);
			default_box_buffer_.setIndexBuffer(5, 3);
			default_box_buffer_.setIndexBuffer(6, 3);
			default_box_buffer_.setIndexBuffer(7, 0);

			default_box_buffer_.setIndexBuffer(8, 4);
			default_box_buffer_.setIndexBuffer(9, 5);
			default_box_buffer_.setIndexBuffer(10, 5);
			default_box_buffer_.setIndexBuffer(11, 6);
			default_box_buffer_.setIndexBuffer(12, 6);
			default_box_buffer_.setIndexBuffer(13, 7);
			default_box_buffer_.setIndexBuffer(14, 7);
			default_box_buffer_.setIndexBuffer(15, 4);

			default_box_buffer_.setIndexBuffer(16, 0);
			default_box_buffer_.setIndexBuffer(17, 4);
			default_box_buffer_.setIndexBuffer(18, 1);
			default_box_buffer_.setIndexBuffer(19, 5);
			default_box_buffer_.setIndexBuffer(20, 2);
			default_box_buffer_.setIndexBuffer(21, 6);
			default_box_buffer_.setIndexBuffer(22, 3);
			default_box_buffer_.setIndexBuffer(23, 7);

			default_box_buffer_.setTFactor(255, 255, 255);

			// load the race track
			ESystem::getSystem().getModule<EFileModule>().getAbsolutePathFromURI(file_res_path_, "ed://asset/modelling/");
			std::string file_name = file_res_path_ + "race_track.dae";
			race_track_ = new ESceneBuffer();
			std::cout << file_name.c_str() << std::endl;
			if (!ESystem::getSystem().getModule<EFileModule>().loadFile(race_track_, file_name.c_str()))
			{
				EASSERT(0);
			}

			// make cmd for light setting
			EVector3 light_dir(0.5f, 0.5f, 1.0f);
			EColor4 light_ambient(127, 127, 127, 255);
			EColor4 light_diffuse(127, 127, 127, 255);
			cmd_for_directional_light_.addCmdLightActivateAsDirectional(0, light_dir.applyNormalization(), light_ambient, light_diffuse);
		}

		void clear()
		{
			entities_.clear();
		}

		Entity& getEntity(const std::string& id)
		{
			return entities_[id];
		}

		void render(EDebugRenderQueue* _render_queue, EGraphicContext* _gc)
		{
			// sets graphic cmd for directional light
			_gc->setGraphicCmdBuffer(cmd_for_directional_light_);

			// render the race track
			for (std::vector<EGeometryBuffer*>::iterator iter = race_track_->getGeometryBuffers().begin(); iter != race_track_->getGeometryBuffers().end(); ++iter)
			{
				EMatrix4 identity_mat_;
				EMatrix4::makeIdentity(identity_mat_);
				_render_queue->renderGeometryBuffer(*iter, identity_mat_);
			}

			// render all of the entities
			for(std::map<std::string, Entity>::iterator itr = entities_.begin(); itr != entities_.end(); ++itr)
			{
				EMatrix4::makeIdentity(box_buffer_mat_);
				EMatrix4::makeTransTM(box_buffer_mat_, EVector3(itr->second.pos));
				_render_queue->renderGeometryBuffer(&default_box_buffer_, box_buffer_mat_);
			}
		}

	private:
		EGraphicCmdBuffer cmd_for_directional_light_;
		std::map<std::string, Entity>	entities_;
		EGeometryBuffer					default_box_buffer_;
		EMatrix4						box_buffer_mat_;
		std::string						file_res_path_;
		ESceneBuffer*					race_track_;
};

//////////////////////////////////////////////////////////////////////

class Nori7ShellResizeEventHandler : public EModule<Nori7ShellResizeEventHandler>
{
	// shell
	class Nori7ShellEventListener : public EShellEventListener
	{
		virtual void onShellResized_(int _width, int _height)
		{
			// with Graphic Device
			EGraphicDevice* graphic_device = ESystem::getSystem().getDevicePtr<EGraphicDevice>();
			if(graphic_device)
			{
				// invalid device
				graphic_device->onDeviceInvalidated();

				// change Dispaly Area ( Context / Camera )
				ESceneModule* scene_module = ESystem::getSystem().getModulePtr<ESceneModule>();
				EASSERT(scene_module);
				EWorldSceneNode* default_world = scene_module->getWorld("default_world");
				EASSERT(default_world);

				// context
				EGraphicContext* displayed_context = default_world->getGraphicContext();
				EASSERT(displayed_context);
				displayed_context->setDisplaySize(_width, _height);

				// camera
				ECameraSceneNode* scene_node_world_camera = default_world->getWorldCamera();
				scene_node_world_camera->setProjection(_width, _height);
			}
		}
	} shell_event_listener_;

	virtual void onCreatedInSystem_(){}
	virtual void onDestroyedInSystem_(){}

	// register handler for shell resize event
	virtual void onRegisterEventDispatcher_(ESystemEventDispatcher& _dispatcher)
	{
		_dispatcher.addShellEventListener(&shell_event_listener_);
	}
};

//////////////////////////////////////////////////////////////////////
//
class Nori7Shell : public EPlatformShell
{
	public:
		Nori7Shell()
		: scene_module_(0), scene_node_world_(0), debug_render_queue_(0)
		, current_world_idx_(0), temp_world_idx_(1)
		{
		}

		virtual ~Nori7Shell()
		{
		}

	protected:

		virtual void onInit_()
		{
			// python
			initPython_();

			// memory debug
			//initMemoryDebug();

			// parent onInit_()
			EPlatformShell::onInit_();

			// creates system
			ESystem::newSystem();

			// begin creation guard
			ESystem::getSystem().lockSystemMutex();

			ESystem::getSystem().createModule<ELogModule>();
			ESystem::getSystem().createModule<EFileModule>();
			ESystem::getSystem().createModule<ETimeModule>();
			ESystem::getSystem().createModule<EProfileModule>();
			ESystem::getSystem().createModule<EMemAllocModule>();
			ESystem::getSystem().getModule<EProfileModule>().setProfileDashboard(1, false);
			ESystem::getSystem().getModule<EFileModule>().addLoader<ESceneBuffer, ESceneBufferColladaLoader>();

			// event handler for resize
			ESystem::getSystem().createModule<Nori7ShellResizeEventHandler>();

			// keyboard / mouse device
			ESystem::getSystem().createDevice<EKeyboardDevice, EPlatformKeyboardDeviceDriver>();
			ESystem::getSystem().createDevice<EMouseDevice, EMouseDeviceDriverGeneral>();

			// end creation guard
			ESystem::getSystem().unlockSystemMutex();

			// shell title
			win_title_ = "Hello end Nonsystem Test";
			width_ = 640;
			height_ = 480;

			entity_world_[0].init();
			entity_world_[1].init();
		}

		virtual void onCanvasEnabled_()
		{
			// creates graphic device / scene module
			ESystem::getSystem().lockSystemMutex();
			EGraphicDevice* graphic_device = ESystem::getSystem().createDevice<EGraphicDevice, EPlatformGraphicDeviceDriver>();
			scene_module_ = ESystem::getSystem().createModule<ESceneModule>();
			ESystem::getSystem().unlockSystemMutex();

			// creates graphic device context
			EPlatformGraphicDeviceDriver* graphic_device_driver = graphic_device->getDriver<EPlatformGraphicDeviceDriver>();
			EHandle<EGraphicContext> gc = graphic_device_driver->createGraphicContext( this, width_, height_, true );

			// creates the world
			scene_node_world_ = scene_module_->createWorld("default_world", gc);

			// inits the world camera (with building render queues
			ECameraSceneNode* camera = scene_node_world_->getWorldCamera();
			EASSERT(camera);

			ERenderQueueBuilder<EDebugRenderStageModule> builder;
			ESystem::getSystem().lockSystemMutex();
			builder.build(camera, true);
			ESystem::getSystem().unlockSystemMutex();
			debug_render_queue_ = camera->getRenderQueue<EDebugRenderQueue>();
			debug_render_queue_->setEnableDefaultBoxRender(false);

			EVector3 pos(10.f, 0.f, 0.f);
			EVector3 dir(-1.f, 0.f, 0.f);
			EMatrix4 mat;
			EMatrix4::makeFrame(mat, pos, dir, EVector3::UNIT_Z);
			camera->setTransformRelativeToWorld(mat);
		}

		virtual void onHalt_()
		{
			EPlatformShell::onHalt_();

			// entity world
			entity_world_[current_world_idx_].clear();
			entity_world_[temp_world_idx_].clear();

			// world
			scene_module_->clearWorlds();
			scene_node_world_ = 0;

			// modules
			ESystem::getSystem().clearSystemObjects();
			scene_module_ = 0;

			// system
			ESystem::deleteSystem();

			// python
			finiPython_();
		}

		virtual void onTick_(float _elapsed_time)
		{
			// checks device input
			EKeyboardDevice* keyboard_device = ESystem::getSystem().getDevicePtr<EKeyboardDevice>();
			EMouseDevice* mouse_device = ESystem::getSystem().getDevicePtr<EMouseDevice>();
			if( 0 != keyboard_device && 0 != mouse_device )
			{
				if( true == keyboard_device->isKeyDown(E_KC_MENU) &&
					true == mouse_device->isCurrentState(E_ME_LBUTTON_DOWN) &&
					true == mouse_device->isCurrentState(E_ME_MOVED) )
				{
					// gets x, y diff
					int pos_x(0), pos_y(0);
					const float mouse_move_factor = -0.01f;
					mouse_device->getDiffPosition(pos_x, pos_y);

					// gets camera transform
					EMatrix4 mat;
					ECameraSceneNode* camera = scene_node_world_->getWorldCamera();
					EASSERT(camera);
					camera->getTransformRelativeToWorld(mat);

					// roates as diff of x by axis z
					EMatrix4 mat_rot;
					EMatrix4::makeRotTM(mat_rot, EVector3::UNIT_Z, float(pos_x) * mouse_move_factor);

					// applyes to position
					EVector3 pos;
					EMatrix4::multiply(pos, mat_rot, *(mat.getFramePosition()));

					// sets direction looking at origin from applied position
					EVector3 dir;
					dir.set(pos).applyNormalization().applySymmetry();

					// roates as diff of y by 'right vector'
					EVector3 right;
					EVector3::crossProduct(right, dir, EVector3::UNIT_Z);
					if(EVector3::lengthSq(right) < 0.001f)
					{
						// guard horizontal up
						pos_y = -pos_y;
						//ELOG("Camera Guarded");
					}

					// matrix for rotation
					right.applyNormalization();
					EMatrix4::makeRotTM(mat_rot, right, float(pos_y) * mouse_move_factor);

					// applies rotation matrix
					dir.set(pos);
					EMatrix4::multiply(pos, mat_rot, dir);
					dir.set(pos).applySymmetry();

					// set transform to the world camera
					EMatrix4::makeFrame(mat, pos, dir, EVector3::UNIT_Z);
					camera->setTransformRelativeToWorld(mat);
				}

				if( true == mouse_device->isCurrentState(E_ME_WHEEL) )
				{
					// gets camera transform
					EMatrix4 mat;
					ECameraSceneNode* camera = scene_node_world_->getWorldCamera();
					EASSERT(camera);
					camera->getTransformRelativeToWorld(mat);

					// applies to position
					EVector3 pos( *mat.getFramePosition() );
					EVector3 dir( *mat.getFrameAxisY() );
					EVector3 dir_scaled(dir);
					pos.applyAddition( dir_scaled.applyScale( mouse_device->getWheelValue() * -1.f ) );

					// set transform to the world camera
					EMatrix4::makeFrame(mat, pos, dir, EVector3::UNIT_Z);
					camera->setTransformRelativeToWorld(mat);
				}
			}

			// serialize end system status
			std::string end_status_xml;
			ESystem::getSystem().serializeSystemStatus(end_status_xml);
			//ELOG(end_status_xml);

			// check and sync python objects
			PyObject* py_end_status_xml = PyString_FromString(end_status_xml.c_str());
			PyObject* py_xml = PyObject_CallFunctionObjArgs(py_tick_function_, py_end_status_xml, NULL);

			if (NULL != py_xml) {
				char* xml_str = PyString_AsString(py_xml);
				if (NULL != xml_str && strlen(xml_str) > 0) {
					parseFromXML(xml_str, entity_world_[temp_world_idx_]);
					entity_world_[current_world_idx_].clear();

					std::swap(temp_world_idx_, current_world_idx_);
				}
			}

			Py_XDECREF(py_xml);
			Py_XDECREF(py_end_status_xml);

			//FPS check
			if(ESystem::getSystem().getModule<EProfileModule>().needToUpdateDashboard())
			{
				unsigned int fps = ESystem::getSystem().getModule<EProfileModule>().updateDashboard();
				EStringUtil strUtil;
				strUtil.formatStr("Nori7 : {0} FPS", fps);
				setShellTitle(strUtil.getString().c_str());
			}

			// Graphic Context for drawing
			EGraphicContext* graphic_context = scene_node_world_->getGraphicContext();

			// starts drawing
			graphic_context->clearBuffers();
			if(graphic_context->beginDrawing())
			{
				scene_node_world_->renderWorld();
				entity_world_[current_world_idx_].render( debug_render_queue_, graphic_context );
			}
			else
			{
				EASSERT(0);
			}

			// ends drawing
			graphic_context->endDrawing();
			graphic_context->flip();
		}

		void parseFromXML(const char* xml_str, World &world)
		{
			EXml xml;
			xml.loadFromString(xml_str);
			EXmlHandler root(xml, true);

			root.pushCurrent();
			bool is_command = root.seekFirstElement("command", 1);
			std::string command = "";
			if (is_command) {
				command = root.getText();
			}
			root.popPrevious();

			if (command == "exit")
			{
				exitShell();
			}

			bool is_entity = root.seekFirstElement("entity", 1);
			while (is_entity)
			{
				root.pushCurrent();
				bool is_id = root.seekFirstElement("id", 1);
				std::string id = "";
				if (is_id) {
					id = root.getText();
				}
				root.popPrevious();

				root.pushCurrent();
				bool is_pos = root.seekFirstElement("pos", 1);
				std::vector<std::string> pos;
				if (is_pos) {
					EStringUtil::split(pos, root.getText());
				}
				root.popPrevious();

				int i = 0; 
				for (std::vector<std::string>::iterator iter = pos.begin(); iter != pos.end(); ++iter)
				{
					Entity& entity = world.getEntity(id);
					entity.pos[i++] = atof((*iter).c_str());
				}

				is_entity = root.seekNextSibling("entity");
			}
		}

	private:
		ESceneModule*		scene_module_;
		EWorldSceneNode* 	scene_node_world_;
		EDebugRenderQueue*	debug_render_queue_;

		// TODO: Replace this mechanism with more natural one
		World				entity_world_[2];
		int					current_world_idx_;
		int					temp_world_idx_;

		PyObject*			py_game_;
		PyObject*			py_tick_function_;

		void initPython_()
		{
			Py_Initialize();
			PyRun_SimpleString("import sys");
			PyRun_SimpleString("sys.path += ['src/scripts']");

			py_game_ = PyImport_ImportModule("game");
			EASSERT(py_game_);
			py_tick_function_ = PyObject_GetAttrString(py_game_, "on_tick");
		}

		void finiPython_()
		{
			Py_XDECREF(py_tick_function_);
			Py_XDECREF(py_game_);

			Py_Finalize();
		}
};

RUN_SHELL( Nori7Shell );

//int main(int argc, char** argv)
//{
//Py_Initialize();

//PyRun_SimpleString("import sys");
//PyRun_SimpleString("sys.path = ['scripts']");

//PyObject* game = PyImport_ImportModule("game");
//assert(game!=NULL);
//PyObject* main = PyObject_GetAttrString(game, "main");
//assert(main!=NULL);
//PyObject* args = PyTuple_New(0);
//PyObject_Call(main, args, NULL);

//Py_Finalize();

//return 0;
//}

// vim: ts=4:sw=4
